
import { App, ALL, Inject, Controller, Provide, Post, Get, Query, Validate, Body, Param } from '@midwayjs/decorator'
import { Application, Context } from 'egg'

import { BaseController } from './base'
import { SystemUserService } from '../service/system-user'
import { UserService } from '../service/user'
import { AuthService } from '../service/auth'
import { LoginDTO, SystemUserDTO, SystemUserUpdateDTO, SystemUserChangePasswordDTO } from '../dto/system-user'
import { UserDTO, UserUpdateDTO, UserChangePasswordDTO } from '../dto/user'
import { CODE } from '../constants/code'

const _ = require('lodash')

@Provide()
@Controller('/api/system')
export class SystemUserController extends BaseController {

  @App()
  app: Application

  @Inject()
  ctx: Context

  @Inject()
  systemUserService: SystemUserService

  @Inject()
  userService: UserService

  @Inject()
  authService: AuthService

  @Post('/login')
  @Validate()
  async login(@Body(ALL) login: LoginDTO) {
    const user = await this.systemUserService.get({ username: login.username })

    if (!user) {
      return this.notOk(CODE.NOT_FOUND)
    }
    if (user.deleted_at) {
      this.ctx.status = 401
      this.ctx.body = this.notOk(CODE.NOT_ALLOWED)
      return
    }

    const isValid = await this.systemUserService.validate(login.password, user.password)

    if (!isValid) {
      return this.notOk(CODE.USER_PASSWORD_INCORRECT)
    }

    const updatedUser = await this.systemUserService.afterLogin(user, login.remember_me)

    return this.ok(updatedUser)
  }

  @Get('/info')
  async getUserInfo() {
    const { id } = this.ctx.state

    if (id) {
      const user = await this.systemUserService.get({ id })

      if (!user) {
        return this.notOk(CODE.NOT_FOUND)
      }
      if (user.deleted_at) {
        this.ctx.status = 401
        this.ctx.body = this.notOk(CODE.NOT_ALLOWED)
        return
      }

      return this.ok(
        _.omit(user, ['password'])
      )
    }

    return this.notOk(CODE.MISSING_TOKEN)
  }

  @Post('/user/create')
  @Validate()
  async addUser(@Body(ALL) user: UserDTO) {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    if (
      user.username &&
      await this.userService.get({ username: user.username })
    ) {
      return this.notOk(CODE.USER_EXISTED)
    }

    if (
      user.phone &&
      await this.userService.get({ phone: user.phone })
    ) {
      return this.notOk(CODE.USER_EXISTED_PHONE)
    }

    if (
      user.email &&
      await this.userService.get({ email: user.email })
    ) {
      return this.notOk(CODE.USER_EXISTED_EMAIL)
    }

    const addedUser = await this.userService.add(user)

    if (addedUser) {
      return this.ok(
        _.omit(addedUser, ['password'])
      )
    }

    return this.notOk(CODE.FAIL)
  }

  @Post('/user/update')
  @Validate()
  async updateUser(@Body(ALL) user: UserUpdateDTO) {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const existedUser = await this.userService.get({ id: user.id })

    if (!existedUser) {
      return this.notOk(CODE.NOT_FOUND)
    }

    if (
      user.username &&
      user.username !== existedUser.username
    ) {
      if (
        await this.userService.get({
          username: user.username
        })
      ) {
        return this.notOk(CODE.USER_EXISTED)
      }
    }

    const updatedUser = await this.userService.update(user)

    return this.ok(
      _.omit(updatedUser, ['password'])
    )
  }

  @Post('/user/:id/:action')
  async deleteOrRevertUser(
    @Param('id') id: string,
    @Param('action') action: 'delete' | 'revert'
  ) {
    const { id: system_user_id, is_system_user } = this.ctx.state

    if (!system_user_id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    if (id) {
      const { id: currentId } = this.ctx.state

      if (!currentId) {
        return this.notOk(CODE.MISSING_TOKEN)
      }

      if (id === currentId) {
        return this.notOk(CODE.NOT_ALLOWED)
      }

      const payload = { id: id }
      const existedUser = await this.userService.get(payload)

      if (existedUser) {
        switch (action) {
          case 'delete': {
            payload['deleted_at'] = 'now()'
            break
          }
          case 'revert': {
            payload['deleted_at'] = null
            break
          }
        }

        if (payload['deleted_at'] !== undefined) {
          await this.userService.update(payload)
          return this.ok()
        }
      }
    }

    return this.notOk(CODE.NOT_FOUND)
  }

  @Post('/user/password')
  @Validate()
  async updateUserPwd(@Body(ALL) user: UserChangePasswordDTO) {
    const existedUser = await this.userService.get({ id: user.id })

    if (!existedUser) {
      return this.notOk(CODE.NOT_FOUND)
    }

    const { old_password, ...toUpdated } = user
    const isValid = this.userService.validate(
      old_password,
      existedUser.password
    )

    if (!isValid) {
      return this.notOk(CODE.USER_PASSWORD_INCORRECT)
    }

    const updatedUser = await this.userService.update(toUpdated)

    return this.ok(
      _.omit(updatedUser, ['password'])
    )
  }

  @Post('/system-user/create')
  @Validate()
  async addSystemUser(@Body(ALL) systemUser: SystemUserDTO) {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    if (
      systemUser.username &&
      await this.systemUserService.get({ username: systemUser.username })
    ) {
      return this.notOk(CODE.USER_EXISTED)
    }

    if (
      systemUser.phone &&
      await this.systemUserService.get({ phone: systemUser.phone })
    ) {
      return this.notOk(CODE.USER_EXISTED_PHONE)
    }

    if (
      systemUser.email &&
      await this.systemUserService.get({ email: systemUser.email })
    ) {
      return this.notOk(CODE.USER_EXISTED_EMAIL)
    }

    const addedUser = await this.systemUserService.add(systemUser)

    if (addedUser) {
      return this.ok(
        _.omit(addedUser, ['password'])
      )
    }

    return this.notOk(CODE.FAIL)
  }

  @Post('/system-user/update')
  @Validate()
  async updateSystemUser(@Body(ALL) systemUser: SystemUserUpdateDTO) {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const existedUser = await this.systemUserService.get({ id: systemUser.id })

    if (!existedUser) {
      return this.notOk(CODE.NOT_FOUND)
    }

    if (
      systemUser.username &&
      systemUser.username !== existedUser.username
    ) {
      if (
        await this.systemUserService.get({
          username: systemUser.username
        })
      ) {
        return this.notOk(CODE.USER_EXISTED)
      }
    }

    const updatedUser = await this.systemUserService.update(systemUser, existedUser)

    return this.ok(
      _.omit(updatedUser, ['password'])
    )
  }

  @Post('/system-user/:id/:action')
  async deleteOrRevertSystemUser(
    @Param('id') id: string,
    @Param('action') action: 'delete' | 'revert'
  ) {
    const { id: sytem_user_id, is_system_user } = this.ctx.state

    if (!sytem_user_id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    if (id) {
      const { id: currentId } = this.ctx.state

      if (!currentId) {
        return this.notOk(CODE.MISSING_TOKEN)
      }

      if (id === currentId) {
        return this.notOk(CODE.NOT_ALLOWED)
      }

      const payload = { id: id }
      const existedUser = await this.systemUserService.get(payload)

      if (existedUser) {
        switch (action) {
          case 'delete': {
            payload['deleted_at'] = 'now()'
            break
          }
          case 'revert': {
            payload['deleted_at'] = null
            break
          }
        }

        if (payload['deleted_at'] !== undefined) {
          await this.systemUserService.update(payload, existedUser)
          return this.ok()
        }
      }
    }

    return this.notOk(CODE.NOT_FOUND)
  }

  @Post('/system-user/password')
  @Validate()
  async updateSystemUserPwd(@Body(ALL) systemUser: SystemUserChangePasswordDTO) {
    const existedUser = await this.systemUserService.get({ id: systemUser.id })

    if (!existedUser) {
      return this.notOk(CODE.NOT_FOUND)
    }

    const { old_password, ...toUpdated } = systemUser
    const isValid = await this.systemUserService.validate(
      old_password,
      existedUser.password
    )

    if (!isValid) {
      return this.notOk(CODE.USER_PASSWORD_INCORRECT)
    }

    const updatedUser = await this.systemUserService.update(toUpdated, existedUser)

    return this.ok(
      _.omit(updatedUser, ['password'])
    )
  }

  @Get('/refresh_token')
  async refreshToken(@Query() token: string) {
    const returning = await this.authService.refreshToken(token)

    if (returning) {
      return this.ok(returning)
    }

    this.ctx.status = 401
    this.ctx.body = this.notOk(CODE.TOKEN_INVALID)
  }

  @Get('/stats')
  async stats() {
    const { id, is_system_user } = this.ctx.state

    if (!id || !is_system_user) {
      return this.notOk(CODE.NOT_ALLOWED)
    }

    const user = await this.systemUserService.get({ id })

    if (user?.system_user_meta?.department?.id) {
      const stats = await this.systemUserService.stats(
        user.system_user_meta.department.id
      )

      if (stats) {
        return this.ok(stats)
      }
    }

    return this.notOk(CODE.FAIL)
  }
}
